﻿--[[
	Widget name	  	Designer
	Author     	 	Kurapica
--]]

-- Designer
do
	local _G = _G
	local UIParent = UIParent
	local _Widget = IGAS.GUI.Widget
	IGAS:NewAddon("IGAS_Studio")
	local L_Designer = IGAS:NewLocal("IGAS_Studio_Designer")

	local _WidgetName = "Designer"
    local _Base = "Frame"

    -- Scripts
	local _meta = {__index = _G}
	local _metaItem = {__mode = "kv"}
	local marker = CreateFrame(Widget.Marker, "Marker", coreF)
	local selector = CreateFrame(Widget.Selector, "Selector", coreF)
	local _NameList = {}
	local _OrderPorperty = {
		"Style",
		"Backdrop",
		"BackdropColor",
		"BackdropBorderColor",
	}

	-- FrameInfoList's Operations
	local _metaName = {
		__index = function(_table, _key)
			if _key == "Name" then
				if rawget(_table, "UsePrefix") then
					return ((rawget(_table, "Parent") and _FrameInfoList[rawget(_table, "Parent")] and _FrameInfoList[rawget(_table, "Parent")].Name) or "").."_"..rawget(_table, "LastName")
				else
					return rawget(_table, "LastName")
				end
			end
		end,

		__newindex = function(_table, _key, _value)
			-- Special key: __Childs
			if _key == "Name" then
				error(_key.." is readonly.", 2)
			end

			rawset(_table,_key,_value)			-- Other key can be set as usual
		end,
	}

	local function errorhandler(err)
		return geterrorhandler()(err)
	end

	local function IsVirtualWidgetType(frameType)
		while frameType and frameType ~= _Widget.VirtualUIObject do
			frameType = frameType.Base
		end
		if frameType and frameType == _Widget.VirtualUIObject then
			return true
		else
			return false
		end
	end

	local function CheckPrefix(frmContainer)
		wipe(_NameList)
		for _, info in pairs(frmContainer.__Item) do
			_NameList[info.LastName] = (_NameList[info.LastName] or 0) + 1
			info.UsePrefix = false
		end
		for _, info in pairs(frmContainer.__Item) do
			if _NameList[info.LastName] > 1 then
				info.UsePrefix = true
			end
		end
		for frame, info in pairs(frmContainer.__Item) do
			frmContainer.Parent.lstVirtual:SetItem(frame, info.Name)
		end
		wipe(_NameList)
	end

	local function AddFrame(frame, parent, name, isContainer)
		parent = parent or frame.Parent
		name = name or frame.Name

		if not _FrameInfoList[frame] then
			_FrameInfoList[frame] = setmetatable({
				["Parent"] = parent,
				["LastName"] = name,
				["Root"] = _FrameInfoList[parent].Root or parent,
				["IsContainer"] = (isContainer and true) or nil,
			}, _metaName)
			_FrameInfoList[frame].Root.__Item[frame] = _FrameInfoList[frame]

			Log(1, "[Designer][AddFrame]".._FrameInfoList[frame].Name)
		else
			_FrameInfoList[frame].LastName = name
		end
		CheckPrefix(_FrameInfoList[frame].Root)
	end

	local function DoRemoveFrame(frame)
		-- Remove Childs
		for f, info in pairs(_FrameInfoList) do
			if info.Parent == frame then
				DoRemoveFrame(f)
			end
		end

		-- Remove self
		local info = _FrameInfoList[frame]
		if info.LastName ~= "UIParent" then
			info.Root.Parent.lstVirtual:RemoveItem(frame)
			info.Root.__Item[frame] = nil
			_FrameInfoList[frame] = nil
			if not info.IsContainer then
				frame:Dispose()
			end
		end
	end

	local function RemoveFrame(frame)
		local root = _FrameInfoList[frame].Root or frame
		DoRemoveFrame(frame)
		CheckPrefix(root)
	end

	local function GetAdvanceContainer(frame)
		frame = GetTrueFrame(frame)

		if Advance[GetTrueObject(frame)] and Advance[GetTrueObject(frame)]["GetContainerList"] then
			local list = Advance[GetTrueObject(frame)]:GetContainerList(frame)

			frame = GetShadowFrame(frame)

			for i, v in pairs(_FrameInfoList) do
				if v.Parent == frame and v.IsContainer and (not list[i]) then
					RemoveFrame(i)
				end
			end
			for i in pairs(list) do
				if not _FrameInfoList[i] then
					AddFrame(i, frame, list[i], true)
					selector:HookFrame(i)
					marker:HookFrame(i, true)
				end
			end
		end
	end

	-- Export Code
	local orderT = {}

	function CopyTable(table)
		local ret = {}

		for i, v in pairs(table) do
			if type(v) == "table" then
				ret[i] = CopyTable(v)
			else
				ret[i] = v
			end
		end

		return ret
	end

	local function RestoreOrderPorperty(frame)
		wipe(orderT)
		orderT["Name"] = true
		orderT["FrameLevel"] = true
		
		frame = GetTrueFrame(frame)
		local objectType = GetTrueObject(frame)
		
		for _, propertyName in ipairs(_OrderPorperty) do
			orderT[propertyName] = true
			local property = objectType.Property[propertyName]
			if property and property["Get"] and property["Set"] then
				local propType = IGAS:GetPropertyType(property.Type)
				local default = GetDefaultValue(objectType, propertyName, frame.Style)

				if propType["Type"] == "number" or propType["Type"] == "string" or propType["Type"] == "enum" then
					if default ~= "" and default ~= 0 then
						frame[propertyName] = GetDefaultValue(GetTrueObject(frame), propertyName)
					end
				elseif propType["Type"] == "boolean" then
					frame[propertyName] = (default and true) or false
				elseif propType["Type"] == "table" then
					if default and default == "table" then
						frame[propertyName] = CopyTable(default)
					--else
					--	frame[propertyName] = nil
					end
				end
			end
		end
	end

	local function RestoreProperty(frame, objectType)
		local property, propType

		if not objectType then
			frame = GetTrueFrame(frame)
			objectType = GetTrueObject(frame)
		end

		if objectType.Base then
			RestoreProperty(frame, objectType.Base, name)
		end

		for propertyName in pairs(objectType.Property) do
			if not orderT[propertyName] then
				orderT[propertyName] = true
				property = GetTrueObject(frame).Property[propertyName]
				propType = IGAS:GetPropertyType(property.Type)

				if propType and property["Get"] and property["Set"] and property["Type"] then
					Log(1, "[Designer][RestoreProperty]"..frame.Name.."."..propertyName)
					local default = GetDefaultValue(GetTrueObject(frame), propertyName, frame.Style)

					if propType["Type"] == "number" or propType["Type"] == "string" or propType["Type"] == "enum" then
						if default ~= "" and default ~= 0 then
							frame[propertyName] = GetDefaultValue(GetTrueObject(frame), propertyName)
						end
					elseif propType["Type"] == "boolean" then
						frame[propertyName] = (default and true) or false
					elseif propType["Type"] == "table" then
						if default and default == "table" then
							frame[propertyName] = CopyTable(default)
						--else
						--	frame[propertyName] = nil
						end
					end
				end
			end
		end
	end

	local function GetOrderPorperty(frame, objectType, codeList, name)
		for _, propertyName in ipairs(_OrderPorperty) do
			orderT[propertyName] = true
			local property = objectType.Property[propertyName]
			if property and property["Get"] and property["Set"] then
				Log(1, "[Designer][GetOrderPorperty]"..name.."."..propertyName)

				local code = SerializeProperty(frame, propertyName, name)
				if code then
					tinsert(codeList, code)
				end
			end
		end
	end

	local function GetPropertyCode(frame, objectType, codeList, name)
		frame = GetTrueFrame(frame)

		for propertyName, property in pairs(objectType.Property) do
			if not orderT[propertyName] then
				orderT[propertyName] = true

				if property["Get"] and property["Set"] then
					Log(1, "[Designer][GetPropertyCode]"..name.."."..propertyName)

					local code = SerializeProperty(frame, propertyName, name)
					if code then
						tinsert(codeList, code)
					end
				end
			end
		end

		if objectType.Base then
			return GetPropertyCode(frame, objectType.Base, codeList, name)
		end
	end

	local function BuildExportList(frame, exportList, checkedList)
		checkedList = checkedList or {}

		if checkedList[frame] then
			return
		end

		local name, info, relativeTo

		name = _FrameInfoList[frame].Name
		info = _FrameInfoList[frame]

		Log(1, "[Designer][BuildExportList] checking "..name)

		checkedList[frame] = true

		if name ~= "UIParent" then
			if not frame:IsObjectType("VirtualUIObject") and frame.GetNumPoints then
				-- First Check Anchor points
				for i = 1, frame:GetNumPoints() do
					_, relativeTo = frame:GetPoint(i)

					if _FrameInfoList[relativeTo] then
						BuildExportList(relativeTo, exportList, checkedList)
					end
				end
			end
		end

		tinsert(exportList, frame)

		exportList[frame] = {}
		for f, info in pairs(_FrameInfoList) do
			if info.Parent == frame then
				BuildExportList(f, exportList[frame], checkedList)
			end
		end
	end

	local function Export(frame, exportList, code)
		local name, info, from, obj, to, x, y, tmpStr

		name = _FrameInfoList[frame].Name
		info = _FrameInfoList[frame]

		Log(1, "[Designer][Export] for "..name)

		if name ~= "UIParent" then
			if not info.IsContainer then
				-- CreateFrame
				tinsert(code, ""..name.." = CreateFrame(\""..GetTrueObject(frame).WidgetName.."\", \""..frame.Name.."\", ".._FrameInfoList[frame.Parent].Name..")")

				-- Point
				if GetTrueFrame(frame):IsObjectType("Region") then
					for i = 1, frame:GetNumPoints() do
						from, obj, to, x, y = frame:GetPoint(i)
						if from and obj and _FrameInfoList[obj] and to and x and y then
							tinsert(code, ""..name..":SetPoint("..Format(from)..", ".._FrameInfoList[obj].Name..", "..Format(to)..", "..Format(x)..", "..Format(y)..")")
						end
					end
				end

				-- Property
				wipe(orderT)
				orderT["Name"] = true
				orderT["Parent"] = true
				GetOrderPorperty(frame, GetTrueObject(frame), code, name)
				GetPropertyCode(frame, GetTrueObject(frame), code, name)

				-- Containers
				if GetTrueFrame(frame).Container then
					tinsert(code, "".._FrameInfoList[GetTrueFrame(frame).Container].Name.." = "..name..".Container")
				end

				if Advance[GetTrueObject(frame)] and Advance[GetTrueObject(frame)]["Serialize"] then
					tmpStr = Advance[GetTrueObject(frame)]:Serialize(GetTrueFrame(frame), name, _FrameInfoList)

					if tmpStr and strtrim(tmpStr) ~= "" then
						tinsert(code, "\n"..tmpStr)
					end
				end
			else
				-- Container's Height
				tinsert(code, ""..name..".Height = "..Format(frame.Height))
			end

			-- Register Event
			if frame.__RegisterEventList and type(frame.__RegisterEventList) == "table" then
				for event in pairs(frame.__RegisterEventList) do
					if event and type(event) == "string" then
						tinsert(code, ""..name..":RegisterEvent(\""..event.."\")")
					end
				end
			end

			if _FrameInfoList[frame].Parent and _FrameInfoList[_FrameInfoList[frame].Parent].Name == "UIParent" then
				tinsert(code, "_Addon:AddFrame("..name..")")
			end

			tinsert(code, "")

			if _FrameInfoList[frame].IsContainer and frame.FixHeight and type(frame.FixHeight) == "function" then
				tinsert(code, ""..name..":FixHeight()\n")
			end
		end

		for i =1, #exportList[frame] do
			Export(exportList[frame][i], exportList[frame], code)
		end
	end

	-- FakeCreateFrame
	local function NewName(_typeN, _parent)
		local i = 1
		local name = _typeN["WidgetName"]

		if not name or name == "" then
			name = "Widget"
		end

		while true do
			if _parent:GetChild(name..i) then
				i = i + 1
			else
				break
			end
		end

		return name..i
	end

	local FakeCreateFrame = function(frmContainer, frameType,frameName,parentFrame,...)
		local frame, useShadow

		parentFrame = IGAS:GetWrapper(parentFrame or UIParent)

		useShadow = false

		-- Pre CreateFrame
		if frmContainer.__Env.__StartLoading then
			for i in pairs(frmContainer.__Item) do
				if i ~= frmContainer then
					GetAdvanceContainer(i)
				end
			end
		end

		if parentFrame == IGAS:GetWrapper(UIParent) then
			parentFrame = frmContainer
		end

		---- Check FrameType
		if not frameType then
			error("There must be a frame's type be set.", 3)
		elseif type(frameType) == "string" then
			if _Widget[frameType] then
				frameType = _Widget[frameType]
			else
				error("This frameType ["..frameType.."] is not exist.", 3)
			end
		elseif type(frameType) == "table" then
			if not frameType["Function"] then
				error("This frameType is invalid.", 3)
			end
		else
			error("This frameType is invalid.", 3)
		end

		if not frameType.New then
			error("This frameType ["..frameType.Name.."] is abstracted.", 3)
		end

		---- Check Parent
		if not parentFrame["AddChild"] then
			error("Can't add child to the object ["..(parentFrame.Name or tostring(parentFrame)).."].", 3)
		end

		---- Check FrameName
		if frameName and type(frameName) ~= "string" then
			error("name must be string.", 3)
		end

		if frameName and frameName ~= "" then
			-- Check the parent's childs
			if parentFrame["GetChild"] and parentFrame:GetChild(frameName) then
				if parentFrame:GetChild(frameName)["GetObjectType"] and parentFrame:GetChild(frameName):GetObjectType() == frameType then
					return parentFrame:GetChild(frameName)
				else
					error("this name ["..frameName.."] is used", 3)
				end
			end
        else
        	frameName = NewName(frameType, parentFrame)
		end

		-- Check if need use ShadowFrame
		if not frameType["Function"]["EnableMouse"] then
			useShadow = true
		end

		-- CreateFrame
		frame = nil

		if not IsVirtualWidgetType(frameType) and useShadow then
			frame = CreateFrame(Widget.ShadowFrame, frameName, parentFrame, frameType, ...)
		else
			frame = CreateFrame(frameType, frameName, parentFrame, ...)
		end

		Log(1, "[Designer][FakeCreateFrame][Type]"..frameType.WidgetName.."[Name]"..frameName)

		-- Post CreateFrame
		if frame then
			-- Mark InDesignMode
			frame.__InDesignMode = true

			-- Set Default value for some property
			local status, err = pcall(RestoreOrderPorperty, GetTrueFrame(frame))
			if status then
				status, err = pcall(RestoreProperty, GetTrueFrame(frame))
			end

			if not status then
				errorhandler(err)
			end

			-- Hook Frame
			if not IsVirtualWidgetType(frameType) then
				if not useShadow then
					selector:HookFrame(frame)
				end
				marker:HookFrame(frame)
			end

			AddFrame(frame, parentFrame)

			-- Mark the Container
			if frame.Container and frame.Container.IsIGASFrame then
				AddFrame(frame.Container, frame, "Container", true)
				selector:HookFrame(frame.Container)
				marker:HookFrame(frame.Container, true)
			end
		end

		return frame
	end

	local function FakeNewAddon()
	end

	local function FakeAddFrame()
	end

	local function CreateNameSpace(frmContainer)
		local _Env = setmetatable({}, _meta)

		-- First of all, install S_UIParent as the UIParent
		_FrameInfoList[frmContainer] = setmetatable({
				["LastName"] = "UIParent",
				["IsContainer"] = true,
		}, _metaName)
		frmContainer.__Item = setmetatable({}, _metaItem)

		_Env.FakeCreateFrame = function(frameType,frameName,parentFrame,...)
			return FakeCreateFrame(frmContainer,frameType,frameName,parentFrame,...)
		end

		_Env.FakeNewAddon = FakeNewAddon

		_Env.FakeAddFrame = FakeAddFrame

		_Env.UIParent = frmContainer

		_Env.L = L_Designer

		return _Env
	end

	local function OnItemChoosed(self, frame)
		marker.MarkedFrame = frame
	end

	-- ScriptType
	local _ScriptType = {
	}

	-- Function
	local _Function = {
        -- Dispose, release resource
        ["Dispose"] = function(self)
			RemoveFrame(self)

            -- Call super's dispose
			self:CallMethod(_Base, "Dispose")
        end,
		["AddFrame"] = function(self, frameType, frameName, parent)
			return self.__Env.FakeCreateFrame(frameType, frameName, parent)
		end,
		["Advance"] = function(self, frame)
			GetAdvanceContainer(frame)
		end,
		["RefreshList"] = function(self)
			CheckPrefix(self)
		end,
		["RemoveFrame"] = function(self, frame)
			if frame then
				RemoveFrame(frame)
			end
		end,
		["Clear"] = function(self)
			RemoveFrame(self)
		end,
		["Load"] = function(self)
			local code = self.__File.Designer or ""
			code = gsub(code, "CreateFrame", "FakeCreateFrame")
			code = gsub(code, "IGAS:NewFrame", "FakeCreateFrame")
			code = gsub(code, "IGAS:NewAddon", "FakeNewAddon")
			code = gsub(code, "_Addon:AddFrame", "FakeAddFrame")

			local f = loadstring(code)

			setfenv(f, self.__Env)

			self.__Env.__StartLoading = true

			local status, err = pcall(f)

			if not status then
				errorhandler(err)
				err = nil
			end

			self.__Env.__StartLoading = nil
		end,
		["Save"] = function(self)
			local code = {}
			local addon = self.__Addon[0].Name
			local name = self.__File.Name
			local exportList = {}

			BuildExportList(self, exportList)

			tinsert(code, "IGAS:NewAddon(\""..addon..".Mdl_"..name.."\")\n")

			for i = 1, #exportList do
				Export(exportList[i], exportList, code)
			end

			self.__File.Designer = table.concat(code, "\n")
		end,
    }

	-- Property
	local _Property = {
    }

    -- New
    local function _New(name, parent, addon, file)
		local frame = CreateFrame("Frame",name,parent)

		frame:SetAllPoints(parent)
		frame.__Addon = addon
		frame.__File = file
		frame.__Env = CreateNameSpace(frame)

		selector:HookFrame(frame)

		local lstVirtual = CreateFrame("List", "lstVirtual", parent)
		lstVirtual:SetPoint("TOPRIGHT", parent, "TOPRIGHT")
		lstVirtual:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT")
		lstVirtual.Width = 150
		lstVirtual.Visible = true
		lstVirtual.OnItemChoosed = OnItemChoosed

        return frame
    end

    local _WidgetInfo = {
        ["WidgetName"] = _WidgetName,
        ["Base"] = _Base,
        ["Function"] = _Function,
        ["Property"] = _Property,
        ["ScriptType"] = _ScriptType,
        ["New"] = _New,
		-- Special Item
    }

    Widget[_WidgetName] = IGAS:NewWidget(_WidgetInfo)
end